/********************************************************************************************************************************
 * 《深入Linux设备驱动程序内核机制》-- 陈学松 著，电子工业出版社， 2012年1月第1次印刷
 * Date: 2024-04-17 11:05
 * Author: dimon.chen 
 * EMali: 1181302388@qq.com 
 * 
 * 章节: 第二章 内核内存管理
 *       
 * 
 ********************************************************************************************************************************/



//参考链接：
//  CNDN博客:https://blog.csdn.net/qq_18380287/article/details/135921438
//  宅学部落 王利涛 , B站学习视频
//


// 使用 alloc_pages() 内核接口函数在内核地址空间中获取多个物理页
//
//


//内核模块总是应该包含这3个头文件
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
//内核内存管理头文件
#include <linux/mm.h>
//使用 alloc_pages() 函数
#include <linux/gfp.h>
//使用 page_to_phys() 函数
#include <asm/page.h>
//使用 ARCH_PFN_OFFSET
#include <linux/pfn.h>
//使用 ARCH_PFN_OFFSET
#include <asm/page.h>
//使用 kmap(), kunmap()
#include <linux/highmem.h>

#define MY_ORDER 5
#define ORDER_HIGH  10


struct page *page = NULL;
unsigned long int virt_addr;

struct page *pageHighMem = NULL;
struct page *pageSingleH = NULL;

int my_module_init(void)
{

    printk("=====alloc_pages() module init=====\n");


    //GFP_MASK 可以是 GFP_KERNEL, GFP_ATOMIC
    // GFP_ATOMIC 申请页面是不允许睡眠，可以用在中断上下文和软中断上下文
    // softirq, tasklet，内核定时器 都是在中断（软中断）上下文中 
    //
    //
    page = alloc_pages(GFP_KERNEL, MY_ORDER);
    if(!page){
        printk("alloc_pages() fail\n");
        return -ENOMEM;
    }
    else{
        printk("alloc_pages() successfully\n");
    }

    printk("page frame no :0x%08lx\n", page_to_pfn(page));
    printk("physical addr :0x%08llx\n", page_to_phys(page));
    printk("virtual addr  :0x%08x\n", (unsigned int)page_address(page));

    //page_to_virt(page);
    //virt_addr = (unsigned long) page_to_virt(page);
    printk("virt addr     :%lx\n", virt_addr);


    printk("===========\n");

    //全局变量 struct page mem_map[] 数组
    //        struct page *mem_map 存放着系统中所有物理页对应的struct page 的数组
    //

    //UMA一致性物理内存模型，只有一个物理内存节点node，
    //UMA一致性物理内存管理，有3中模型
    //1. FLAT 平坦地址模型，物理内存是连续的没有空洞
    //2. DISCONTIGOUS 非连续地址模型，物理内存是不连续的，物理内存地址空间存在空洞
    //3. SPARSE 稀疏地址模型，物理内存是不连续的。如果要支持Hotplugin热插拔，需要使用 sparse 稀疏
    //   地址模型
    //



    //
    //查看当前正在运行的 linux 内核的编译 config 参数
    // 1. 如果 /proc 目录下有文件 config.gz。可以用 unzip /proc/config.gz 查看当前正在运行内核
    //    的编译参数
    // 2. 如果不存在 /proc/config.gz 文件，例如在我的环境中 X86 32bit Ubuntu-14.04 虚拟机中
    //    

    // 通过查看Linux-2.26.39 的源码，可以看到只有在使用内存物理地址平坦（Flat)模型下，才使用到了
    // struct page mem_map[]; (struct page *mem_map;)
    // 当前我正在使用的 Intelx86 32bit Unbuntu 14.04 kernel-4.04 内核，内核编译时的参数是
    // CONFIG_SPARSEMEM （物理内存地址稀疏模型）
    // 在CONFIG_SPARSEMEM 内存物理地址稀疏模型下，物理内存地址是不连续的中间有空洞，所以
    // 内核全局变量 struct page *mem_map; 值为 NULL，
    //
    // struct page *page = alloc_pages(GFP_KERNEL, order=3);
    // pfn = page_to_pfn(page);
    // 在物理内存地址平坦模型下
    // #define page_to_pfn(page) ((unsigned long)((page)-mem_map) + ARCH_PFN_OFFSET)
    // 


    //这里打印出 struct page *mem_map 指针的值为NULL，因为我当前Unbutu 14.04 X86 32bit 内核，
    //内核编译时的编译参数选择的是 UMA CONFIG_SPARSEMEM 稀疏物理地址模型。
    printk("mem_map\t\t\t:%p\n", mem_map);
    printk("page\t\t\t:%p\n", page);
    printk("page-mem_map\t\t:0x%08x\n", page-mem_map);
    //__free_pages(page, MY_ORDER);



    //打印 ARCH_PFN_OFFSET
    //打印 PAGE_OFFSET
    //打印 PAGE_SHIFT

#if defined(CONFIG_FLATMEM)
    printk("ARCH_PFN_OFFSET: 0x%x\n", ARCH_PFN_OFFSET);
#endif
    printk("PAGE_OFFSET:     0x%lx\n", PAGE_OFFSET);
    printk("PAGE_SHIFT:      0x%x\n", PAGE_SHIFT);





    ///使用 PageHighMem() 函数判断返回的 struct page *page 指针是否是在物理内存高端内存区
    ///ZONE_HIGHMEM
    ///#include <linux/mm.h>
    ///#incldue <linux/mm_types.h>
    ///


    

    // printk("GFP_KERNEL|__GFP_HIGHMEM, order=9\n");
    // pageHighMem = alloc_pages(GFP_KERNEL, ORDER_HIGH);
    printk("GFP_KERNEL, order=5\n");
    pageHighMem = alloc_pages(GFP_KERNEL, ORDER_HIGH);
    if(!pageHighMem){
        printk("alloc pages from ZONE_HIGHMEM by gfp_mask: __GFP_HIGHMEM fail\n");
        __free_pages(page, MY_ORDER);
        return -ENOMEM;
    }
    else{
        printk("alloc pages from ZONE_HIGHMEM by gfp_mask: __GFP_HIGHMEM success\n");
    }

    printk("pageHighMem page:\t\t:0x%p\n", pageHighMem);
    printk("PageHighMem(pageh) = %d\n", PageHighMem(pageHighMem));
    printk("pageHighMem     pfn:\t\t:0x%08lx\n", page_to_pfn(pageHighMem));
    printk("pageHighMem phyaddr:\t\t:0x%08llx\n", page_to_phys(pageHighMem));


    /// 在系统中CPU访问的都是虚拟地址，所以把 struct page *page ， 使用 page_address() 获取
    /// 到page对应的虚拟地址
    ///
    //virt_addr
    virt_addr = (unsigned long) page_address(pageHighMem);
    unsigned char *ptr = (unsigned char *)virt_addr;
    memset(ptr, 'B', 1024*8);

    ptr = (unsigned char *)virt_addr + 1024*4;
    int i = 0;
    for(i = 0; i<16; i++){
        printk("%p %c %02x\n", ptr, *ptr, *ptr);
        ptr++;
    }



    //通过 GIF_KERNEL | __GFP_HIGHMEM
    // gfp_mask __GFP_HIGHMEM 申请到 ZONE_HIGHMEM高端内存区
    //
    printk("GFP_KERNEL|__GFP_HIGHMEM, order=9\n");
    pageSingleH = alloc_page(GFP_KERNEL|__GFP_HIGHMEM);
    // printk("GFP_KERNEL, order=5\n");
    // pageHighMem = alloc_pages(GFP_KERNEL, ORDER_HIGH);
    if(!pageSingleH){
        printk("alloc pages from ZONE_HIGHMEM by gfp_mask: __GFP_HIGHMEM fail\n");
        __free_pages(page, MY_ORDER);
        return -ENOMEM;
    }
    else{
        printk("alloc pages from ZONE_HIGHMEM by gfp_mask: __GFP_HIGHMEM success\n");
    }

    printk("pageSingleH page:\t\t:0x%p\n", pageSingleH);
    printk("pageSingleH(pageh) = %d\n", PageHighMem(pageSingleH));
    printk("pageSingleH     pfn:\t\t:0x%08lx\n", page_to_pfn(pageSingleH));
    printk("pageSingleH phyaddr:\t\t:0x%08llx\n", page_to_phys(pageSingleH));


    /// 在系统中CPU访问的都是虚拟地址，所以把 struct page *page ， 使用 page_address() 获取
    /// kmap() 一次只能映射一个物理内存page 
    ///
    ptr = kmap(pageSingleH);
    memset(ptr, 'C', 1024*2);
    for(i = 0; i<16; i++){
        printk("%p %c %02x\n", ptr, *ptr, *ptr);
        ptr++;
    }



    return 0;
}

void my_module_exit(void)
{
    __free_pages(page, MY_ORDER);
    __free_pages(pageHighMem, ORDER_HIGH);

    kunmap(pageSingleH);
    __free_page(pageSingleH);

    printk("alloc_pages() module exit\n");
}


module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("dimon.chen 1181302388@qq.com");
MODULE_DESCRIPTION("alloc and free physicall pages from system ZONE_NORMAL");